package service

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"io"

	"gitee.com/anxu/golang-web/src/config"
	"golang.org/x/crypto/bcrypt"
)

// EncryptKey  default key
const EncryptKey = "QRQYS6MC7RCD7BFU5N5HXZLE"

// Security security
var Security *security

type security struct {
	key []byte
}

func init() {
	key := config.Config.Get("security.key", EncryptKey).(string)
	Security = &security{key: []byte(key)}
}

func (s *security) GenerateRandomKey(length int) []byte {
	res := make([]byte, length)
	if _, err := io.ReadFull(rand.Reader, res); err != nil {
		panic(err)
	}
	return res
}

// Encrypt 返回加密后字符串，一般是 加密向量 + 加密后数据 [hashkey][iv][ciphertext]
func (s *security) Encrypt(text []byte) []byte {

	block, err := aes.NewCipher(s.key)
	if err != nil {
		panic(err)
	}

	blockSize := block.BlockSize()
	// 加密数据长度必须为密码块长度的整数倍，如果不是整数倍，则需要填充
	text = padding(text, blockSize)

	ciphertext := make([]byte, blockSize+len(text))
	// 填充 加密向量
	iv := ciphertext[:blockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		panic(err)
	}

	mode := cipher.NewCBCEncrypter(block, iv)
	mode.CryptBlocks(ciphertext[blockSize:], text)

	return ciphertext
}

func padding(text []byte, blockSize int) []byte {
	padding := blockSize - len(text)%blockSize
	// 多填充一个
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(text, padtext...)
}

func (s *security) Decrypt(ciphertext []byte) []byte {

	block, err := aes.NewCipher(s.key)
	if err != nil {
		panic(err)
	}

	blockSize := block.BlockSize()

	iv := ciphertext[:blockSize]
	ciphertext = ciphertext[blockSize:]

	mode := cipher.NewCBCDecrypter(block, iv)
	mode.CryptBlocks(ciphertext, ciphertext)

	return unpadding(ciphertext)
}

func unpadding(text []byte) []byte {
	l := len(text)
	index := int(text[l-1])
	return text[:(l - index)]
}

// GeneratePasswordHash a secure hash from a password.
func (s *security) GeneratePasswordHash(password []byte) []byte {

	str, _ := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
	return str
}

// ValidatePassword Verifies a password against a hash.
func (s *security) ValidatePassword(password, hash []byte) bool {

	err := bcrypt.CompareHashAndPassword(hash, password)
	if err == nil {
		return true
	}
	return false
}

//HashData  Compute the data hash value
func (s *security) HashData(data []byte) []byte {

	return hashData(data, s.key)
}

//ValidateData  Validates if the given data is tampered.
func (s *security) ValidateData(data, hashed []byte) bool {

	expected := hashData(data, s.key)
	return hmac.Equal(hashed, expected)
}

func hashData(data, key []byte) []byte {

	mac := hmac.New(sha256.New, key)
	return mac.Sum([]byte(data))
}
